from functools import cached_property
import re
from typing import Any, Iterable
from typing_extensions import Self
from pydantic import BaseModel
from pysam import TabixFile, VariantRecord  # pylint: disable=E0611

from .transcript import Transcript, Interval


class CNV(BaseModel):
    chrom: str
    start: int
    end: int
    info: dict[str, Any] = {}

    @cached_property
    def pk(self) -> str:
        return f"{self.chrom}:{self.start}-{self.end}"

    @cached_property
    def size(self):
        return self.end - self.start

    @classmethod
    def from_variant_record(cls, record: VariantRecord) -> Self:
        return cls(chrom=record.chrom, start=record.start, end=record.stop)

    def validate_overlap(self, interval: Interval, overlap: float) -> int:
        overlap_size = min(self.end, interval.end) - max(self.start, interval.start)
        return self.size > 0 and interval.size > 0 and min(overlap_size / self.size, overlap_size / interval.size) >= overlap

    def query_tabix(self, tbx: TabixFile) -> Iterable[str]:
        try:
            return tbx.fetch(self.chrom, self.start, self.end)
        except Exception:  # pylint: disable=W0718
            return []

    def anno_gene(self, tbx: TabixFile, gene_ids: dict[str, str]):
        data = {}
        for line in self.query_tabix(tbx):
            transcript = Transcript.from_genepred_line(line)
            gene_id = gene_ids.get(transcript.gene_pk, ".")
            detail = transcript.calc_detail(self.start, self.end)
            data.setdefault(transcript.gene_pk, {"gene": transcript.gene, "gene_id": gene_id, "detail": ""})
            sep = "|" if data[transcript.gene_pk]["detail"] else ""
            data[transcript.gene_pk]["detail"] += f"{sep}{detail}"
        if data:
            genes, gene_ids, details = list(zip(*map(lambda x: (x["gene"], x["gene_id"], x["detail"]), data.values())))
            self.info.update({"GENE": genes, "GENE_ID": gene_ids, "DETAIL": details})

    def anno_cytoband(self, tbx: TabixFile):
        records = list(map(Interval.from_cytoband_line, self.query_tabix(tbx)))
        if records:
            start_name, end_name = records[0].name, records[-1].name
            name = start_name if start_name == end_name else f"{start_name}{end_name}"
            self.info.update({"LOCATION": re.sub("[Cc][Hh][Rr]", "", f"{self.chrom}{name}")})

    def anno_bed(self, tbx: TabixFile, name: str, overlap: float = -1):
        # decipher, clinvar, dgv, local: overlap=overlap
        # clingen: overlap=-1
        records = list(filter(lambda x: self.validate_overlap(x, overlap), map(Interval.from_bed_line, self.query_tabix(tbx))))
        if records:
            self.info.update({name: "|".join(list(map(lambda x: x.name, records)))})
